#include "illumination.hpp"

Illumination::Illumination(void) { }

Illumination::~Illumination(void) { }

Color Illumination::calculate_color_rec(const int _object, const Ray& _ray, Vector _point, Scene* _scene, const unsigned short _depth) const {
	Color color = calculate_color(_object,_ray,_point,_scene);
	Color color_;
	if(!_depth) return color;
	
	Ray ray_ = reflected_ray(_ray, _point, _scene->get_object(_object)->get_normal(_point));
	Vector point_;
	int object_ = _scene->nearest_object(point_,ray_);
	if (object_ != -1) {	 
		color_ = calculate_color_rec(object_,ray_,point_,_scene, _depth - 1);

	}
        color.set(color+color_);
	return color;
}
Color Illumination::calculate_color(const int _index,const Ray& _ray,const Vector& _point,Scene* _scene) const {
    
	const double attenuation= _scene->get_attenuation(_point.sub_vect(_ray.get_origin()).lenght());
	
	Color cor;
	double diffuse = 0.0,specular = 0.0;
	/*Getting the object and the material*/
	Object* object = _scene->get_object(_index);
	Material* object_material = object->get_material();
		
	/*Material attributes*/
	const Color matAmb = object_material->get_ambient();
	const Color matDif = object_material->get_diffuse();
	const Color matSpe = object_material->get_specular();
	const double brightness = object_material->get_brightness();

	/* Ambient*/
	double ambient = _scene->get_ambient(); 
	Vector normal = object->get_normal(_point);
	
	for(int l=0;l<_scene->getLightesEnd();l++) { /*For each light, calculate specular and diffuse */
            
		Light* light = _scene->get_light(l);


		/*Light attributes*/

		Material* light_material = light->get_material();
		const Color luzAmb = light_material->get_ambient();
		const Color luzDif = light_material->get_diffuse();
		const Color luzSpe = light_material->get_specular();

		/*Light Vector (L) */
		const Vector L = light_vector(light,_point);

		/*Testing occlusion object */

		/* Occluse object -> no diffuse and specular calculation */
		if(shadow_testing(_scene,light,_point)) {
			cor.set_r(cor.get_r() +
			attenuation*(
			luzAmb.get_r()*matAmb.get_r()*ambient
			));
			cor.set_g(cor.get_g() +
			attenuation*(
			luzAmb.get_g()*matAmb.get_g()*ambient
			));
			cor.set_b(cor.get_b() +
			attenuation*(
			luzAmb.get_b()*matAmb.get_b()*ambient
			));
		}
		else {
			/* Diffuse */
			diffuse = calculate_diffuse(L,normal);

			/*  Specular */
			specular = calculate_specular(L,normal,_ray);

			diffuse = max(0.0,diffuse);
			specular = pow(max(0.0,specular),brightness);

			cor.set_r(cor.get_r() +
					attenuation*(
					luzSpe.get_r()*matSpe.get_r()*specular+
					luzDif.get_r()*matDif.get_r()*diffuse+
					luzAmb.get_r()*matAmb.get_r()*ambient
					));
			cor.set_g(cor.get_g() +
					attenuation*(
					luzSpe.get_g()*matSpe.get_g()*specular+
					luzDif.get_g()*matDif.get_g()*diffuse+
					luzAmb.get_g()*matAmb.get_g()*ambient
					));
			cor.set_b(cor.get_b() +
					attenuation*(
					luzSpe.get_b()*matSpe.get_b()*specular+
					luzDif.get_b()*matDif.get_b()*diffuse+
					luzAmb.get_b()*matAmb.get_b()*ambient
					));
		}
	}
	return cor;
}

Vector Illumination::calculate_reflection(const Vector& _incident,const Vector& normal) const {
	return  (normal.scalar_product(normal.dot_product(_incident)*2.0)).sub_vect(_incident);
}

Vector Illumination::light_vector(Light* _light,const Vector& _point) const {
    return _light->get_origin().sub_vect(_point).normalize();
}

Ray Illumination::reflected_ray(const Ray& _ray,const Vector& _point,const Vector& _normal) const{ 
	/* Prevent auto-reflection */
	const Vector point = _point+(_normal*0.001);
	
	const Vector incidente = _ray.get_direction().scalar_product(-1.0);
	const Vector reflection = calculate_reflection(incidente,_normal.normalize());
	return Ray(point,reflection);
}

double Illumination::calculate_diffuse(const Vector& L, const Vector& _normal) const {
	return L.dot_product(_normal);	
}

double Illumination::calculate_specular(const Vector& L,const Vector& _normal,const Ray& _ray) const {

	Vector reflexao = calculate_reflection(L,_normal);
	return (_ray.get_direction().scalar_product(-1.0)).dot_product(reflexao);
	
}

bool Illumination::shadow_testing(Scene* _scene,Light* _light,const Vector& _point) const {
    Ray shadow_testing(_point,_light->get_origin().sub_vect(_point));
				
    const double distance = shadow_testing.get_direction().lenght();
	
	/* Prevent auto-shadowing */
    shadow_testing.set_origin(shadow_testing.get_origin()+(shadow_testing.get_direction()*0.001));
	
    shadow_testing.set_direction(shadow_testing.get_direction().normalize());
    return _scene->object_occlusion(shadow_testing,distance);
}
    
